package gnss

import (
	"dgo/goutils/utils"
	"fmt"
	"sort"
	"strings"
	"time"
)

/**
 * 一个历元的数据, 包含多个系统
 *  未正式使用 (2019-09-03 17:44:32)
 *  推荐使用RtcmMsmEpoch
 */
type ObsEpoch struct {
	TOW        int64
	ObsMap     map[byte][]*RtcmMsm
	OnEpochEnd func()
	lastRtcmT  time.Time
}

func NewObsEpoch() *ObsEpoch {
	rval := &ObsEpoch{
		ObsMap: make(map[byte][]*RtcmMsm),
	}
	return rval
}

func (this *ObsEpoch) CheckAddObs(msm *RtcmMsm) {
	if this.OnEpochEnd == nil {
		this.AddObs(msm)
		this.lastRtcmT = msm.MsmTime
		return
	}

	if !msm.MsmTime.IsZero() && len(this.ObsMap) > 0 && this.lastRtcmT != msm.MsmTime {
		this.OnEpochEnd()
		this.Reset()
	}

	this.AddObs(msm)
	this.lastRtcmT = msm.MsmTime

	if msm.Header.Sync == 0 {
		this.OnEpochEnd()
		this.Reset()
	}
	return
}

func (this *ObsEpoch) CheckDone() {
	if this.OnEpochEnd != nil && len(this.ObsMap) > 0 {
		this.OnEpochEnd()
		this.Reset()
	}

}

func (this *ObsEpoch) AddObs(msm *RtcmMsm) *RtcmMsm {
	msmlst := this.ObsMap[msm.Sys]
	if msmlst == nil {
		msmlst = make([]*RtcmMsm, 1, 1)
		msmlst[0] = msm
		this.ObsMap[msm.Sys] = msmlst
		return msm
	}

	//for _, v := range msmlst {
	//	if v.Header.MsgId == msm.Header.MsgId {
	//		return v
	//	}
	//}

	msmlst = append(msmlst, msm)
	this.ObsMap[msm.Sys] = msmlst
	return msm
}

func (this *ObsEpoch) Reset() {
	this.ObsMap = make(map[byte][]*RtcmMsm)
}

func (this *ObsEpoch) msmToString(msm *RtcmMsm, sb *strings.Builder) {

	//msm.ToString(sb);
	//return;

	// 只输出信号强度(SNR)
	var i, j byte
	// 循环卫星
	for i = 0; i < msm.Header.SatN; i++ {
		prnId := msm.Header.Sats[i]
		strPrn := GetPrnStrCode(prnId, msm.Sys)
		sb.WriteString(strPrn)
		sb.WriteString(" ")

		// 循环信号
		for j = 0; j < msm.Header.SigN; j++ {
			iSign := msm.Header.Sigs[j]
			strBrandId := SigiBrandID(msm.Sys, iSign)
			sb.WriteString(fmt.Sprintf("%s %.2f ", strBrandId, msm.ObsV[i].Snr[j]))
		}
		sb.WriteString("\n")
	}
}

/*
**

	signHeader: 信号数组
*/
func (this *ObsEpoch) msmToRinex3String(msm *RtcmMsm, sb *strings.Builder, signHeader []byte) int {
	var i byte

	r := 0

	signIdxMap := make(map[byte]byte)

	// 观测值中的信号 创建信号->索引数组
	for x := byte(0); x < msm.Header.SigN; x++ {
		signIdxMap[msm.Header.Sigs[x]] = x + 1
	}

	// 循环卫星
	for i = 0; i < msm.Header.SatN; i++ {
		prnId := msm.Header.Sats[i]
		strPrn := GetPrnStrCode(prnId, msm.Sys)
		sb.WriteString(strPrn)
		sb.WriteString(" ")
		obsV := msm.ObsV[i] // 一颗卫星的观测值数据
		for j := 0; j < len(signHeader); j++ {
			str := ""
			signV := signHeader[j]
			idx := signIdxMap[signV]
			v1 := float64(0)
			v2 := float64(0)
			v3 := float64(0)
			v4 := float64(0)
			if idx > 0 {
				idx = idx - 1
				v1 = obsV.PseV[idx]
				v2 = obsV.L[idx]
				v3 = obsV.Doppler[idx]
				v4 = obsV.Snr[idx]
			}

			str += PrintRinexFV(v1, 3, 13) + " "
			str += "  " //LLI SSI
			str += PrintRinexFV(v2, 3, 13) + " "
			str += "  " //LLI SSI
			str += PrintRinexFV(v3, 3, 13) + " "
			str += "  " //LLI SSI
			str += PrintRinexFV(v4, 3, 13) + " "
			str += "  " //LLI SSI
			sb.WriteString(str)
		}
		sb.WriteString("\n")

		r++
	}

	return r
}

func (this *ObsEpoch) Foreach(cb func(sys byte, msm *RtcmMsm)) int {
	r := 0
	msmlst := this.ObsMap[SYS_BD]
	if msmlst != nil {
		for _, v := range msmlst {
			cb(SYS_BD, v)
			r++
		}
	}

	msmlst = this.ObsMap[SYS_GPS]
	if msmlst != nil {
		for _, v := range msmlst {
			cb(SYS_BD, v)
			r++
		}
	}

	msmlst = this.ObsMap[SYS_GAL]
	if msmlst != nil {
		for _, v := range msmlst {
			cb(SYS_BD, v)
			r++
		}
	}
	msmlst = this.ObsMap[SYS_GLO]
	if msmlst != nil {
		for _, v := range msmlst {
			cb(SYS_BD, v)
			r++
		}
	}
	return r
}

/*
*

	{"Beidou":{"C01":{"B1":44,"B1C":0,"B2":47,"B2a":0,"B3":45.4375}
*/
func (this *ObsEpoch) writeJSONSNRString(sb *utils.JSONBuilder, lst []*RtcmMsm) {
	if len(lst) == 0 {
		return
	}

	// map[satid]map[sign]snr
	snrMap := make(map[byte]map[byte]float64)
	sign := make([]byte, 0, MAX_SIGNA_N)
	for _, v := range lst {
		sign = v.CheckAddSign(sign)
		v.WriteSNR2Map(snrMap)
	}

	// 写入信号头
	sys := lst[0].Sys
	sb.AddKey(GetNavSysName(sys))
	sb.ObjectBegin()
	n := 0
	for prnId, rec := range snrMap {
		strPrn := GetPrnStrCode(prnId, sys)
		if n > 0 {
			sb.Spliter()
		}
		sb.AddKey(strPrn)
		sb.ObjectBegin()
		j := 0
		for _, sigv := range sign {
			v1 := rec[sigv]
			if v1 != 0 {
				if j > 0 {
					sb.Spliter()
				}
				sb.Add(SigiBrandID(sys, sigv), fmt.Sprintf("%.2f", v1))
				j++
			}
		}
		sb.ObjectEnd()
		n++
	}

	sb.ObjectEnd()
}

func (this *ObsEpoch) writeSimpleSNRString(sb *strings.Builder, lst []*RtcmMsm) {
	if len(lst) == 0 {
		return
	}

	sign := make([]byte, 0, MAX_SIGNA_N)
	for _, v := range lst {
		sign = v.CheckAddSign(sign)
	}

	// 写入信号头
	sys := lst[0].Sys
	sb.WriteString(GetNavSysName(sys))
	sb.WriteString(";")
	for j := 0; j < len(sign); j++ {
		iSign := byte(sign[j])
		strBrandId := SigiBrandID(sys, iSign)
		if j > 0 {
			sb.WriteString(",")
		}
		sb.WriteString(strBrandId)
	}
	sb.WriteString(";")

	for _, v := range lst {
		v.WriteSimpleSNREx(sb, sign)
	}
}

func (this *ObsEpoch) writeSNRString(sb *strings.Builder, lst []*RtcmMsm) {

	for _, v := range lst {
		v.WriteBrandSNR(sb)
	}
}

func (this *ObsEpoch) writeRinex3String(sb *strings.Builder, lst []*RtcmMsm, signHeader []byte) int {
	r := 0
	for _, v := range lst {
		r += this.msmToRinex3String(v, sb, signHeader)
	}
	return r
}

func (this *ObsEpoch) lstToMsmString(sb *strings.Builder, lst []*RtcmMsm) {
	for _, v := range lst {
		this.msmToString(v, sb)
	}
}

func (this *ObsEpoch) SatIDList() string {
	idmap := make(map[string]byte)
	this.Foreach(func(sys byte, msm *RtcmMsm) {
		// 循环卫星
		var i byte
		for i = 0; i < msm.Header.SatN; i++ {
			prnId := msm.Header.Sats[i]
			strPrn := GetPrnStrCode(prnId, msm.Sys)
			idmap[strPrn] = 1
		}
	})

	ids := make([]string, 0, len(idmap))
	for k, _ := range idmap {
		ids = append(ids, k)
	}
	sort.Strings(ids)
	var sb strings.Builder
	for k, v := range ids {
		if k > 0 {
			sb.WriteString(",")
		}
		sb.WriteString(v)
	}
	return sb.String()
}

func (this *ObsEpoch) StatSNRNum(val float64) int {
	statMap := make(map[byte]map[byte]int)
	for _, msmlst := range this.ObsMap {
		if msmlst != nil {
			for _, m := range msmlst {
				m.StatBySNR(statMap, val)
			}
		}
	}

	r := 0
	for _, itm := range statMap {
		r += len(itm)
	}

	return r

}

/*
*
BeiDou:25
GPS:9
GLONASS:9
GALILEO:8
*/
func (this *ObsEpoch) ToStatSNRString(val float64) string {
	statMap := make(map[byte]map[byte]int)

	var sb strings.Builder
	for _, msmlst := range this.ObsMap {
		if msmlst != nil {
			for _, m := range msmlst {
				m.StatBySNR(statMap, val)
			}
		}
	}

	statFunc := func(sys byte) {
		sysMap := statMap[sys]
		if sysMap != nil {
			sb.WriteString(GetNavSysName(sys))
			sb.WriteString(":")
			sb.WriteString(fmt.Sprintf("%d", len(sysMap)))
			sb.WriteString("\n")
		}
	}

	statFunc(SYS_BD)
	statFunc(SYS_GPS)
	statFunc(SYS_GLO)
	statFunc(SYS_GAL)
	return sb.String()
}

/*
*
// {"BeiDou":{"C01":{"B1":44,"B1C":0,"B2":47,"B2a":0,"B3":45.4375}...
*/
func (this *ObsEpoch) ToJSONSNRString() string {
	var sb utils.JSONBuilder
	n := 0
	var checkFunc = func() {
		if n == 0 {
			sb.ObjectBegin()
		} else {
			sb.Spliter()
		}
	}
	msmlst := this.ObsMap[SYS_BD]
	if msmlst != nil {
		checkFunc()
		this.writeJSONSNRString(&sb, msmlst)
		n++
	}

	msmlst = this.ObsMap[SYS_GPS]
	if msmlst != nil {
		checkFunc()
		this.writeJSONSNRString(&sb, msmlst)
		n++
	}
	//
	msmlst = this.ObsMap[SYS_GAL]
	if msmlst != nil {
		checkFunc()
		this.writeJSONSNRString(&sb, msmlst)
		n++
	}
	//
	msmlst = this.ObsMap[SYS_GLO]
	if msmlst != nil {
		checkFunc()
		this.writeJSONSNRString(&sb, msmlst)
		n++
	}
	if n > 0 {
		sb.ObjectEnd()
	}
	return sb.String()
}

/*
*
BeiDou;B1,B1C,B3,B2,B2a;C01|43.75,0.00,43.50,46.12,0.00;C02|42.31,0.00,44.69,49.06,0.00;
*/
func (this *ObsEpoch) ToSimpleSNRString() string {
	var sb strings.Builder
	msmlst := this.ObsMap[SYS_BD]
	if msmlst != nil {
		this.writeSimpleSNRString(&sb, msmlst)
		sb.WriteString("\n")
	}

	msmlst = this.ObsMap[SYS_GPS]
	if msmlst != nil {
		this.writeSimpleSNRString(&sb, msmlst)
		sb.WriteString("\n")
	}

	msmlst = this.ObsMap[SYS_GAL]
	if msmlst != nil {
		this.writeSimpleSNRString(&sb, msmlst)
		sb.WriteString("\n")
	}

	msmlst = this.ObsMap[SYS_GLO]
	if msmlst != nil {
		this.writeSimpleSNRString(&sb, msmlst)
		sb.WriteString("\n")
	}
	return sb.String()
}

/*
*
G04 L1 44.44 L2 42.00 L2 44.94 L5 48.38
G08 L1 46.06 L2 47.81 L2 45.06 L5 47.19
G09 L1 39.06 L2 41.94 L2 37.88 L5 42.75
G16 L1 44.06 L2 41.19 L2 0.00 L5 0.00
G22 L1 40.06 L2 37.00 L2 0.00 L5 0.00
*/
func (this *ObsEpoch) ToSNRString() string {
	var sb strings.Builder
	msmlst := this.ObsMap[SYS_BD]
	if msmlst != nil {
		this.writeSNRString(&sb, msmlst)
	}

	msmlst = this.ObsMap[SYS_GPS]
	if msmlst != nil {
		this.writeSNRString(&sb, msmlst)
	}

	msmlst = this.ObsMap[SYS_GAL]
	if msmlst != nil {
		this.writeSNRString(&sb, msmlst)
	}

	msmlst = this.ObsMap[SYS_GLO]
	if msmlst != nil {
		this.writeSNRString(&sb, msmlst)
	}
	return sb.String()
}

func (this *ObsEpoch) ToRinex3String(signHeader *RinexHeader) (int, string) {
	var sb strings.Builder
	r := 0
	lst := this.ObsMap[SYS_BD]
	if lst != nil {
		r += this.writeRinex3String(&sb, lst, signHeader.CHeader)
	}

	lst = this.ObsMap[SYS_GPS]
	if lst != nil {
		r += this.writeRinex3String(&sb, lst, signHeader.GHeader)
	}

	lst = this.ObsMap[SYS_GAL]
	if lst != nil {
		r += this.writeRinex3String(&sb, lst, signHeader.EHeader)
	}

	lst = this.ObsMap[SYS_GLO]
	if lst != nil {
		r += this.writeRinex3String(&sb, lst, signHeader.GloHeader)
	}
	return r, sb.String()
}

func (this *ObsEpoch) String() string {
	var sb strings.Builder
	lst := this.ObsMap[SYS_BD]
	if lst != nil {
		//sb.WriteString(fmt.Sprintf("BDS:%d\n", msm.Header.satN));
		//msm.ToString(&sb);
		this.lstToMsmString(&sb, lst)
	}

	lst = this.ObsMap[SYS_GPS]
	if lst != nil {
		//sb.WriteString(fmt.Sprintf("GPS:%d\n", msm.Header.satN));
		//msm.ToString(&sb)
		this.lstToMsmString(&sb, lst)
	}

	lst = this.ObsMap[SYS_GLO]
	if lst != nil {
		//sb.WriteString(fmt.Sprintf("GLO:%d\n", msm.Header.satN));
		//msm.ToString(&sb)
		this.lstToMsmString(&sb, lst)
	}

	lst = this.ObsMap[SYS_GAL]
	if lst != nil {
		//sb.WriteString(fmt.Sprintf("GAL:%d\n", msm.Header.satN));
		//msm.ToString(&sb)
		this.lstToMsmString(&sb, lst)
	}
	return sb.String()
}
